#!/usr/bin/python
'''
Mulitprocess version of client_stub
'''
import socket
import struct
import select
import os
import sys
import logging
import traceback
import random
#we need just one connection for ssh, all other connection is combined to ssh tunnel
#only support NO AUTHENTICATION(X'00')
#only support tcp
#only support CONNECT(X'02') CMD

LISTENADDR='0.0.0.0'
PORT=80
PIDFILE='/var/run/htserver.pid'
LOGFILE='/var/log/htserver.log'
ALLOWEDIP=['211.160.165.186','192.168.1.7','192.168.1.77','192.168.1.8','192.168.1.88','192.168.1.9','192.168.1.99','127.0.0.1']
KEYS=['Zmpsd28AXDkyMzA5NDjvdzl3']
RUNUID=65534

reloadcfgflg=None
quitflg=None
clients=None

class ConnectClosed(Exception):pass

log=logging.info
#log=lambda x: None

def quitht():
    global clients
    for c in clients:
        try:
            os.kill(c, 9)
        except:
            pass
    log("quit...")
    sys.exit(0)

def reloadcfg():
    log("configuration reloaded")

def read(sk, cnt):
    buf=''
    while len(buf)<cnt:
        tp = sk.recv(cnt-len(buf))
        if not tp:
            raise ConnectClosed
        buf += tp
    return buf

def readline(sk):
    buf=''
    c=None
    while c!='\n':
        c = sk.recv(1)
        if not c:
            raise ConnectClosed
        buf += c
    return buf

def run(port):
    global reloadcfgflg
    global quitflg
    global clients
    global LISTENADDR
    try:
        sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    except Exception,e:
        log('Error while setting listening socket options.')
        sys.exit(1)
    try:
        sk.bind((LISTENADDR, port))
    except Exception,e:
        log('Error while bind listening socket to %s:%d.' % (LISTENADDR,port))
        sys.exit(1)
    sk.listen(10)
    os.setuid(RUNUID)
    log('Listener pid: %d listening on port %d' % (os.getpid(), port))
    clients = []
    while True:
        try:
            cs, client = sk.accept()
        except IOError,e:
            if e.errno==4:
                if quitflg:
                    quitht()
                    quitflg=False
                if reloadcfgflg:
                    reloadcfg()
                    reloadcfgflg=False
                continue
            else:
                raise e
        except Exception,e:
            log('Listener Error while accept: %s' % e)
            sk.shutdown(socket.SHUT_WR)
            sk.close()
            return 1
        if client[0] not in ALLOWEDIP:
            log('rejected connection from %s:%d' % client)
            cs.close()
            cs = None
            continue
        log('client %s:%d connected.' % client)
        pid = os.fork()
        if pid:
            # parent process
            log('client %s:%d child process %d' % (client[0], client[1], pid))
            clients.append(pid)
            cs.close()
            cs = None
        else:
            # child process
            clients = None
            try:
                # child process
                sk.close()
                # parse data from http request
                # request line, get port
                line = readline(cs)
                port, key = line.split('/')[1:3]
                port = int(port)
                # key line
                if not key in KEYS:
                    raise Exception('Key %s not allowed'%key)
                # host line, get host
                line = readline(cs)
                host = line.split(':')[1].strip('\r\n').strip()
                # all remain line
                while line != '\r\n':
                    line = readline(cs)
                addr = (host, port)
                log('client %s:%d request connect to %s:%d' % (client[0], client[1], addr[0], addr[1]))
                #connect to addr
                ss = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                ss.connect(addr)
                log('client %s:%d connected to %s:%d' % (client[0], client[1], addr[0], addr[1]))
                # reply to client
                cs.send('HTTP/1.1 200 OK\r\nContent-Length: %d\r\nConnection: keep-alive\r\n\r\n'%random.randint(1024**3,2*(1024**3)))
                #exchange data
                while True:
                    rss,ws,xss = select.select([cs,ss],[],[cs,ss])
                    if cs in xss:
                        c = cs.recv(1, socket.MSG_OOB)
                        if not c:
                            ss.close()
                            cs.close()
                            raise ConnectClosed('eof while recv oob data from client')
                        ss.send(c, socket.MSG_OOB)
                    if ss in xss:
                        c = ss.recv(1, socket.MSG_OOB)
                        if not c:
                            ss.close()
                            cs.close()
                            raise ConnectClosed('eof while recv oob data from server')
                        cs.send(c, socket.MSG_OOB)
                    if cs in rss:
                        rs = cs
                        ws = ss
                        buf = rs.recv(4096)
                        if not buf:
                            ss.close()
                            cs.close()
                            raise ConnectClosed('eof while recv data from client')
                        ws.send(buf)
                    if ss in rss:
                        rs = ss
                        ws = cs
                        # encapsulate into http traffic to cheat firewall
                        buf = rs.recv(4096)
                        if not buf:
                            cs.close()
                            ss.close()
                            raise ConnectClosed('eof while recv data from server')
                        ws.send(buf)
            except ConnectClosed,e:
                log('client %s:%d closed. %s ' % (client[0], client[1], e))
                return 0
            except Exception,e:
                log('client %s:%d exception catched: %s' % (client[0], client[1], traceback.format_exc()))
                return 1
def daemonize():
    if os.fork():
        sys.exit(0)
    os.setsid()
    #fork again
    if os.fork():
        sys.exit(0)
    os.chdir('/')
    os.umask(0)
    sys.stdin.close()
    sys.stdout.close()
    sys.stderr.close()
    #catch some signal
    import signal
    def sigint_func(x, frame):
        global reloadcfgflg
        log("sigint catched")
        reloadcfgflg=True
    signal.signal(signal.SIGINT, sigint_func)
    def sigterm_func(x, frame):
        global quitflg
        log("sigterm catched")
        quitflg=True
    signal.signal(signal.SIGTERM, sigterm_func)
    signal.signal(signal.SIGCHLD, signal.SIG_IGN)

def main():
    global LISTENADDR
    #check for root privilege
    if os.geteuid() != 0:
        sys.stderr.write('Launch failed: must be run as root.\n')
        sys.exit(1)
    #get listening addr
    if len(sys.argv)>1:
        LISTENADDR=sys.argv[1]
    daemonize()
    logging.basicConfig(level=logging.DEBUG,
                        format='%(asctime)s %(levelname)s %(message)s',
                        filename=LOGFILE)
    open(PIDFILE,'w').write('%d' % os.getpid())
    run(PORT)

if __name__ == '__main__':
    main()

